package org.nhindirect.stagent.bridge.james.mailet;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;

import javax.mail.MessagingException;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.ParseException;

import org.apache.mailet.Mail;
import org.apache.mailet.base.GenericMailet;
import org.nhindirect.stagent.Body;
import org.nhindirect.stagent.EntitySerializer;
import org.nhindirect.stagent.Header;
import org.nhindirect.stagent.HeaderCollection;
import org.nhindirect.stagent.MimeEntity;
import org.nhindirect.stagent.NHINDAgent;
import org.nhindirect.stagent.cert.ICertificateService;
import org.nhindirect.stagent.cert.impl.KeyStoreCertificateService;
import org.nhindirect.stagent.trust.impl.UniformTrustSettings;

import org.nhindirect.stagent.Protocol;

//import org.nhindirect.stagent.utils.TestUtils;


/** 
 * @author gmeyer@cerner.com
 *
 */
public class PostfixNHINDFilter extends GenericMailet
{
	private NHINDAgent agent;
	private Set<String> internalHISPDomains = new HashSet<String>();
	
	private String incomingHost;
	private String incomingPort;
	private String outgoingHost;
	private String outgoingPort;
	private boolean passThroughLocalRecips = true;
	
	public void init() throws ParseException
	{
		System.out.println("Initializing PostfixNHINDFilter");
		
		/*
		 * TODO: implement configurable pattern to create store instances
		 */
		try
		{
			String domains = getInitParameter("InternalHispDomains");
			String trustCertAliases = getInitParameter("TrustAnchorAliases");
			
			Collection<X509Certificate> archorCerts = new ArrayList<X509Certificate>();
			
			StringTokenizer tokenizer = new StringTokenizer(domains, ",");
			while (tokenizer.hasMoreTokens())
				internalHISPDomains.add(tokenizer.nextToken());
			
			outgoingHost = getInitParameter("OutgoingHost");
			outgoingPort = getInitParameter("OutgoingPort");

			incomingHost = getInitParameter("IncomingHost");
			incomingPort = getInitParameter("IncomingPort");
			
			System.out.println("OutgoingHost: " + outgoingHost);
			System.out.println("OutgoingPort: " + outgoingPort);
			System.out.println("IncomingHost: " + incomingHost);
			System.out.println("IncomingPort: " + incomingPort);
			
			ICertificateService certService = null;
			
			String certStoreClassName = getInitParameter("CertStoreImpl");
			
			System.out.println("CertStoreImpl name: " + certStoreClassName);
			
			Class<?> certStoreClass = Class.forName(certStoreClassName);
			Object certStoreImpl = certStoreClass.newInstance();
			if (certStoreImpl instanceof KeyStoreCertificateService)
			{
				KeyStoreCertificateService certServiceHolder = (KeyStoreCertificateService)certStoreImpl;
				certServiceHolder.setKeyStoreFile(getInitParameter("KeyStoreFile"));
				certServiceHolder.setKeyStorePassword(getInitParameter("KeyStorePW"));	
				certServiceHolder.setPrivateKeyPassword(getInitParameter("KeyStorePKPW"));
				
				certServiceHolder.loadKeyStore();
				
				certService = certServiceHolder;
				
				System.out.println("Successfully create cert store");
				
				tokenizer = new StringTokenizer(trustCertAliases, ",");
				while (tokenizer.hasMoreTokens())
				{
					String token = tokenizer.nextToken();
					X509Certificate anchorCert = certServiceHolder.getByAlias(token);
					if (anchorCert != null)
					{
						System.out.println("Loading archor CN: " + anchorCert.getSubjectDN().getName());
						archorCerts.add(anchorCert);
					}
				}
			}
			
			System.out.println("Creating NHIND agent.");
			
			// hard coded to use the uniform trust settings
			
			agent = new NHINDAgent(internalHISPDomains.iterator().next(),  certService, 
					certService, new UniformTrustSettings(archorCerts));					
			
			System.out.println("Successfully create NHIND agent");
			
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
	}
	
	public void service(Mail mail) throws MessagingException
	{		
		System.out.println("Handling incoming message.");
		
		String domain = "";
		MimeMessage msg = mail.getMessage();		
		
		
		// determine if this message needs to be encrypted/signed or decryped/verified
		InternetAddress sender = mail.getSender().toInternetAddress();//(InternetAddress)msg.getSender();

		int index = sender.getAddress().indexOf("@"); 
		if (index > -1)
			domain = sender.getAddress().substring(index + 1);
		
		if (domain.length() > 0 && !internalHISPDomains.contains(domain))
		{
			// incoming message came from another HISP source or potentially an untrusted source
			// ensure it is an smime signed message
			try
			{
			
				if (msg.getContentType().contains("multipart/signed") || msg.getContentType().contains(Protocol.EncryptedContentMediaTypeAlternative) || 
						msg.getContentType().contains(Protocol.EncryptedContentMediaType))
				{
					System.out.println("Processing incoming message from external HISP.");					
					//System.out.println("Incoming message content:\r\n" +  mimeMessageToString(msg));
					
					
					String strippedMessage = agent.processIncoming(msg);
					
					System.out.println("Message stripped.  Sending to forwarding service.");
					
					forwardMessageToSMTP(strippedMessage, false);
				}
				else
				{
					System.out.println("Message did not originate from this HISP and is not a signed message.  Discarding message.");
					
					// bail... this is not a valid incoming message
				}
			}
			catch (Throwable e)
			{
				e.printStackTrace();
			}
		}
		else
		{			
			// mail is originating from inside the HISP
			try
			{
				if (msg.getContentType().contains("multipart/signed") || msg.getContentType().contains(Protocol.EncryptedContentMediaTypeAlternative) || 
						msg.getContentType().contains(Protocol.EncryptedContentMediaType))
				{
					// this is a signed message originating from our HISP
					// decrypt it and move it on
					System.out.println("Processing signed message from internal HISP.");

					String strippedMessage = agent.processIncoming(msg);
					
					forwardMessageToSMTP(strippedMessage, false);
					
				}
				else
				{
					/*
					 * TODO:  Need to handle situations where recipients may include internal domain accounts.  Some
					 * email deployments may drop the messages directly into mailboxes which will leave encrypted messages
					 * in the mailboxes.
					 */
					System.out.println("Processing outgoing message.  Sign and encrypt.");					
					
					String encryptedAndSignedMessage = agent.processOutgoing(mimeMessageToString(msg));		
					System.out.println("\r\n\r\n\r\n" + encryptedAndSignedMessage);
					forwardMessageToSMTP(encryptedAndSignedMessage, true);
				}
			}
			catch (Exception e)
			{
				e.printStackTrace();
			}
			

		}
		
		System.out.println("Exiting service.");
		
		mail.setState(Mail.GHOST);
	}
	
	private void forwardMessageToSMTP(String rawMessage, boolean outgoing)
	{
		try
		{
			
	        Properties p = new Properties();
	        
	        if (outgoing)
	        {
	        	p.put("mail.smtp.host",  (outgoingHost == null || outgoingHost.length() == 0) ? "localhost" : outgoingHost);
	        	p.put("mail.smtp.port", (outgoingPort == null || outgoingPort.length() == 0) ? "25" : outgoingPort);
	        }
	        else
	        {
	        	p.put("mail.smtp.host",  (incomingHost == null || incomingHost.length() == 0) ? "localhost" : incomingHost);
	        	p.put("mail.smtp.port", (incomingPort == null || incomingPort.length() == 0) ? "25" : incomingPort);
	        }
	        	
	        System.out.println("Forwording processed message to " + p.getProperty("mail.smtp.host") + ":" +  p.getProperty("mail.smtp.port"));
	        
	        // start a session
	        javax.mail.Session session = javax.mail.Session.getInstance(p, null);
	
	        // create the message
	        MimeMessage message = new MimeMessage(session, new ByteArrayInputStream(rawMessage.getBytes("ASCII")));	        
	        
	        Transport trans = session.getTransport("smtp");
	        trans.connect();
	        
            message.saveChanges();
            trans.sendMessage(message, message.getAllRecipients());
            trans.close();
	        
		}
		catch (Throwable e)
		{
			e.printStackTrace();
		}
	}
	
	private String mimeMessageToString(MimeMessage msg)
	{
		String retVal = "";
		try
		{
			byte buffer[] = new byte[2048];
			InputStream inStream = msg.getRawInputStream();
			ByteArrayOutputStream oStream = new ByteArrayOutputStream();
			int count;
			while ((count = inStream.read(buffer)) > -1)
				oStream.write(buffer, 0, count);
			
			MimeEntity theEntity = new MimeEntity();// EntitySerializer.Default.deserialize(msg.getInputStream());
			
    	    Enumeration en = msg.getAllHeaders();
    	    HeaderCollection headers = new HeaderCollection();
    	    while (en.hasMoreElements())
    	    {
    	    	javax.mail.Header hd = (javax.mail.Header)en.nextElement();
    	    	headers.add(new Header(hd.getName(), hd.getValue()));	        	    	
    	    }
    	    theEntity.setHeaders(headers);
    	        	    
    	    Body bdy = null;
    	    Header encoding = theEntity.getContentTransferEncoding();
    	    
    	    String str = new String(oStream.toByteArray());
    	    

   	    	bdy = new Body(new String(oStream.toByteArray(), "ASCII"));
    	    	
    	    theEntity.setBody(bdy);	
    	    
    	    retVal = EntitySerializer.Default.serialize(theEntity);
		}
		catch (Exception e)
		{
			
		}
		
		return retVal;
	}
	
}

